<?php

declare(strict_types=1);

namespace Imi\Workerman\Process;

use Channel\Client;
use Imi\App;
use Imi\Config;
use Imi\Event\Event;
use Imi\Log\Handler\ConsoleHandler;
use Imi\Log\Log;
use Imi\Log\Logger;
use Imi\Process\Event\ProcessBeginEvent;
use Imi\Process\Event\ProcessEndEvent;
use Imi\RequestContext;
use Imi\Server\Event\PipeMessageEvent;
use Imi\Util\Imi;
use Imi\Util\Process\ProcessAppContexts;
use Imi\Util\Process\ProcessType;
use Imi\Worker as ImiWorker;
use Imi\Workerman\Process\Contract\IProcess;
use Imi\Workerman\Server\WorkermanServerWorker;
use Imi\Workerman\Util\Imi as UtilImi;
use Symfony\Component\Console\Output\StreamOutput;
use Workerman\Worker;

/**
 * 进程管理类.
 */
class ProcessManager
{
    use \Imi\Util\Traits\TStaticClass;

    private static array $map = [];

    /**
     * 进程数组.
     *
     * @var Worker[]
     */
    private static array $processes = [];

    public static function getMap(): array
    {
        return self::$map;
    }

    public static function setMap(array $map): void
    {
        self::$map = $map;
    }

    /**
     * 增加映射关系.
     */
    public static function add(string $name, string $className, array $options): void
    {
        self::$map[$name] = [
            'className' => $className,
            'options'   => $options,
        ];
    }

    /**
     * 获取配置.
     */
    public static function get(string $name): ?array
    {
        return self::$map[$name] ?? null;
    }

    /**
     * 实例化新的进程.
     */
    public static function newProcess(string $name, array $args = [], ?string $alias = null): Worker
    {
        $options = self::get($name);
        if (null === $options)
        {
            throw new \RuntimeException(sprintf('Process %s config not found', $name));
        }
        if (isset(self::$processes[$name]))
        {
            throw new \RuntimeException(sprintf('Process %s is exists', $name));
        }
        $processName = $alias ?? $name;
        self::$processes[$processName] = $worker = new WorkermanServerWorker();
        $worker->name = $processName;
        $worker->reloadable = false;
        $worker->onWorkerStart = static function (Worker $worker) use ($args, $processName, $options): void {
            // 随机数播种
            mt_srand();
            Imi::loadRuntimeInfo(Imi::getCurrentModeRuntimePath('runtime'));

            App::set(ProcessAppContexts::PROCESS_TYPE, ProcessType::PROCESS, true);
            App::set(ProcessAppContexts::PROCESS_NAME, $processName, true);

            UtilImi::setProcessName('process', [
                'processName'   => $processName,
            ]);

            if (WorkermanServerWorker::$daemonize)
            {
                /** @var Logger $loggerInstance */
                $loggerInstance = App::getBean('Logger');
                foreach ($loggerInstance->getLoggers(true) as $logger)
                {
                    foreach ($logger->getHandlers() as $handler)
                    {
                        if ($handler instanceof ConsoleHandler)
                        {
                            $handler->setOutput($stdoutStream ??= new StreamOutput(fopen(WorkermanServerWorker::$stdoutFile, 'a')));
                        }
                    }
                }
            }

            Log::info('Process start [' . $processName . ']. pid: ' . getmypid());

            RequestContext::muiltiSet([
                'worker' => $worker,
            ]);

            // 多进程通讯组件连接
            $channel = Config::get('@app.workerman.channel');
            if ($channel)
            {
                Client::connect($channel['host'] ?: '127.0.0.1', $channel['port'] ?: 2206);
                // 监听进程通讯
                $callback = static function (array $data): void {
                    $action = $data['action'] ?? null;
                    if (!$action)
                    {
                        return;
                    }
                    Event::dispatch(new PipeMessageEvent('imi.pipe_message.' . $action, [
                        'data' => $data,
                    ]));
                };
                $workerId = ImiWorker::getWorkerId();
                Client::on('imi.process.message.' . $processName . '.' . $workerId, $callback);
                Client::on('imi.process.message.' . $workerId, $callback);
            }

            // 进程开始事件
            Event::dispatch(new ProcessBeginEvent($processName, $worker));
            try
            {
                // 执行任务
                /** @var IProcess $processInstance */
                $processInstance = App::newInstance($options['className'], $args);
                $processInstance->run($worker);
            }
            catch (\Throwable $th)
            {
                Log::error($th);
            }
            finally
            {
                // 进程结束事件
                Event::dispatch(new ProcessEndEvent($processName, $worker));
                Log::info('Process stop [' . $processName . ']. pid: ' . getmypid());
            }
        };

        return $worker;
    }

    /**
     * 获取进程.
     */
    public static function getProcess(string $name): Worker
    {
        if (!isset(self::$processes[$name]))
        {
            throw new \RuntimeException(sprintf('Process %s not found', $name));
        }

        return self::$processes[$name];
    }
}
